using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

using Microsoft.Xna.Framework;

using JigLibX.Physics;
using JigLibX.Collision;
using JigLibX.Geometry;
using JigLibX.Math;

namespace Alex_s_Dream_The_Toy_World
{
    /// <summary>
    /// An implementation of <see cref="IPhysicsScene"/> using JigLibX.
    /// </summary>
    public class JigLibXScene : IPhysicsScene
    {
        private float fixedStepFrequency = 1 / 30.0f;

        private PhysicsSystem jigLibXPhysics;
        private float totalDt = 0.0f;
        private bool bFixedTimeMet = false;
        private Thread processingThread;

        private ManualResetEvent startFrame;
        private ManualResetEvent endFrame;
        private ManualResetEvent endThread;

        private List<JigLibXActor> deletionList;

        /// <summary>
        /// Gets/sets the gravity vector.
        /// </summary>
        public Vector3 Gravity
        {
            get
            {
                return jigLibXPhysics.Gravity;
            }
            set
            {
                jigLibXPhysics.Gravity = value;
            }
        }

        /// <summary>
        /// Constructs a new physics scene.
        /// </summary>
        /// <param name="game">A reference to the current <see cref="QSGame"/> instance.</param>
        internal JigLibXScene()
        {
            // Create JigLibX scene with standard settings.
            // TODO:  Expose some of these settings in a generic way.
            jigLibXPhysics = new PhysicsSystem();
            jigLibXPhysics.CollisionSystem = new CollisionSystemGrid(32, 32, 4, 13, 13, 13);
            jigLibXPhysics.EnableFreezing = true;
            jigLibXPhysics.SolverType = PhysicsSystem.Solver.Normal;
            jigLibXPhysics.CollisionSystem.UseSweepTests = true;
            jigLibXPhysics.AllowedPenetration = 0.0001f;

            deletionList = new List<JigLibXActor>();

            // Create synchronization handles
            startFrame = new ManualResetEvent(false);
            endFrame = new ManualResetEvent(false);
            endThread = new ManualResetEvent(false);

            // Create and start the physics processing thread.
            processingThread = new Thread(SceneProcessing);
            processingThread.Priority = ThreadPriority.Normal;
            processingThread.IsBackground = false;
            processingThread.Start();
        }

        /// <summary>
        /// Set the simulations update frequency
        /// </summary>
        /// <param name="stepsPerSecond"></param>
        public void SetPhysicsTimeStep( int stepsPerSecond )
        {
            fixedStepFrequency = 1.0f / stepsPerSecond;
        }

        /// <summary>
        /// Creates a new physics actor.
        /// </summary>
        /// <param name="desc">The actor descriptor.</param>
        /// <returns>The new actor instance.</returns>
        public IPhysicsActor CreateActor(ActorDesc desc)
        {
            if (this.bFixedTimeMet)
            {
                // This should pause the physics frame ever so slightly so we can add the
                // actor to the physics scene.
                this.startFrame.Reset();
            }

            IPhysicsActor actor = new JigLibXActor(desc);

            if (this.bFixedTimeMet)
            {
                // Unpause physics now that new actor is in the physics scene
                this.startFrame.Set();
            }

            return actor;
        }

        /// <summary>
        /// Schedule an actor for deletion. Deletion will occur before the next physics simulation step.
        /// </summary>
        /// <param name="actor">Actor to delete</param>
        public void ScheduleForDeletion(IPhysicsActor actor)
        {
            if (actor is JigLibXActor)
            {
                deletionList.Add((actor as JigLibXActor));
            }
        }
        
        /// <summary>
        /// Begins physics processing.
        /// </summary>
        /// <param name="gameTime">The XNA <see cref="GameTime"/> structure for this frame.</param>
        public void BeginFrame(GameTime gameTime)
        {
            for (int i = deletionList.Count - 1; i > 0; --i)
            {
                RemoveActor( deletionList[i] );
                deletionList.RemoveAt( i );
            }

            // Save the time delta and signal the physics thread to start processing.
            this.totalDt += (float)gameTime.ElapsedGameTime.TotalMilliseconds / 1000.0f;

            // We want a steady 30hz physics simulation, don't run physics this frame if it has been less than 1/30th of a second
            if (totalDt >= fixedStepFrequency)
            {
                this.bFixedTimeMet = true;

                this.startFrame.Set();
            }
            else
            {
                this.bFixedTimeMet = false;
            }

            
        }

        /// <summary>
        /// Blocks until the current frame's processing is complete.
        /// </summary>
        public void EndFrame()
        {
            if (bFixedTimeMet)
            {
                // Block until the physics frame is finished processing.
                this.endFrame.WaitOne(Timeout.Infinite, false);
                this.endFrame.Reset();
            }
        }

        /// <summary>
        /// Releases all unmanaged resources for the scene.
        /// </summary>
        public void Dispose()
        {
        }

        /// <summary>
        /// Kills the physics processing thread.
        /// </summary>
        public void KillProcessingThread()
        {
            endThread.Set();
        }

        /// <summary>
        /// The main scene processing thread process.
        /// </summary>
        private void SceneProcessing()
        {
#if XBOX360
            Thread.CurrentThread.SetProcessorAffinity(new int[] {4});
#endif //XBOX360
            try
            {
                // Execute until we are exiting.
                while(!endThread.WaitOne(0, false))
                {
                    // Wait for frame start event.
                    if(!startFrame.WaitOne(10, false))
                    {
                        continue;
                    }

                    // Reset the event.
                    startFrame.Reset();

                    int steps = 0;

                    // We only run the physics simulation if there is still enough time left to
                    // simulation, and we do not allow more than a few steps per frame, otherwise
                    // a low framerate could lead to many physics steps, which would lower framerate
                    // even further.
                    while (steps < 3 && (totalDt >= fixedStepFrequency) )
                    {
                        // Process the frame.
                        jigLibXPhysics.Integrate(fixedStepFrequency);

                        totalDt -= fixedStepFrequency;
                        ++steps;
                    }

                    // Signal frame completion.
                    endFrame.Set();
                }
            }
            catch(ThreadAbortException)
            {
            }
        }

        /// <summary>
        /// Remove an actor from the physics simulation
        /// </summary>
        /// <param name="actor"></param>
        private void RemoveActor(JigLibXActor actor)
        {
            actor.RemoveFromSimulation();
        }
    }
}
